Passed
Branch v8.x (3f6847)
by Rafael S.
02:03
created

wavheader.js ➔ getDwChannelMask_   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 13
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 21
rs 8.6666
1
/*
2
 * Copyright (c) 2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview A tool to create wav file headers.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/**
31
 * Audio formats.
32
 * Formats not listed here will be set to 65534,
33
 * the code for WAVE_FORMAT_EXTENSIBLE
34
 * @enum {number}
35
 * @private
36
 */
37
export const AUDIO_FORMATS = {
38
  '4': 17,
39
  '8': 1,
40
  '8a': 6,
41
  '8m': 7,
42
  '16': 1,
43
  '24': 1,
44
  '32': 1,
45
  '32f': 3,
46
  '64': 3
47
};
48
49
/**
50
 * Return the header for a wav file.
51
 * @param {string} bitDepthCode The audio bit depth
52
 * @param {number} numChannels The number of channels
53
 * @param {number} sampleRate The sample rate.
54
 * @param {number} numBytes The number of bytes each sample use.
55
 * @param {number} samplesLength The length of the samples in bytes.
56
 * @param {!Object} options The extra options, like container defintion.
57
 * @private
58
 */
59
export function wavHeader(bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
60
  let header = {};
0 ignored issues
show
Unused Code introduced by
The assignment to variable header seems to be never used. Consider removing it.
Loading history...
61
  if (bitDepthCode == '4') {
62
    header = createADPCMHeader_(
63
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
64
65
  } else if (bitDepthCode == '8a' || bitDepthCode == '8m') {
66
    header = createALawMulawHeader_(
67
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
68
69
  } else if(Object.keys(AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
70
      numChannels > 2) {
71
    header = createExtensibleHeader_(
72
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
73
74
  } else {
75
    header = createPCMHeader_(
76
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);      
77
  }
78
  return header;
79
}
80
81
/**
82
 * Validate the header of the file.
83
 * @throws {Error} If any property of the object appears invalid.
84
 * @private
85
 */
86
export function validateHeader_(header) {
87
  validateBitDepth_(header);
88
  validateNumChannels_(header);
89
  validateSampleRate_(header);
90
}
91
92
/**
93
 * Validate the bit depth.
94
 * @return {boolean} True is the bit depth is valid.
95
 * @throws {Error} If bit depth is invalid.
96
 * @private
97
 */
98
function validateBitDepth_(header) {
99
  if (!AUDIO_FORMATS[header.bitDepth]) {
100
    if (parseInt(header.bitDepth, 10) > 8 &&
101
        parseInt(header.bitDepth, 10) < 54) {
102
      return true;
103
    }
104
    throw new Error('Invalid bit depth.');
105
  }
106
  return true;
107
}
108
109
/**
110
 * Validate the number of channels.
111
 * @return {boolean} True is the number of channels is valid.
112
 * @throws {Error} If the number of channels is invalid.
113
 * @private
114
 */
115
function validateNumChannels_(header) {
116
  /** @type {number} */
117
  let blockAlign = header.fmt.numChannels * header.fmt.bitsPerSample / 8;
118
  if (header.fmt.numChannels < 1 || blockAlign > 65535) {
119
    throw new Error('Invalid number of channels.');
120
  }
121
  return true;
122
}
123
124
/**
125
 * Validate the sample rate value.
126
 * @return {boolean} True is the sample rate is valid.
127
 * @throws {Error} If the sample rate is invalid.
128
 * @private
129
 */
130
function validateSampleRate_(header) {
131
  /** @type {number} */
132
  let byteRate = header.fmt.numChannels *
133
    (header.fmt.bitsPerSample / 8) * header.fmt.sampleRate;
134
  if (header.fmt.sampleRate < 1 || byteRate > 4294967295) {
135
    throw new Error('Invalid sample rate.');
136
  }
137
  return true;
138
}
139
140
/**
141
 * Create the header of a linear PCM wave file.
142
 * @param {string} bitDepthCode The audio bit depth
143
 * @param {number} numChannels The number of channels
144
 * @param {number} sampleRate The sample rate.
145
 * @param {number} numBytes The number of bytes each sample use.
146
 * @param {number} samplesLength The length of the samples in bytes.
147
 * @param {!Object} options The extra options, like container defintion.
148
 * @private
149
 */
150
function createPCMHeader_(bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
151
  return {
152
    container: options.container,
153
    chunkSize: 36 + samplesLength,
154
    format: 'WAVE',
155
    bitDepth: bitDepthCode,
156
    fmt: {
157
      chunkId: 'fmt ',
158
      chunkSize: 16,
159
      audioFormat: AUDIO_FORMATS[bitDepthCode] ? AUDIO_FORMATS[bitDepthCode] : 65534,
160
      numChannels: numChannels,
161
      sampleRate: sampleRate,
162
      byteRate: (numChannels * numBytes) * sampleRate,
163
      blockAlign: numChannels * numBytes,
164
      bitsPerSample: parseInt(bitDepthCode, 10),
165
      cbSize: 0,
166
      validBitsPerSample: 0,
167
      dwChannelMask: 0,
168
      subformat: []
169
    }
170
  };
171
}
172
173
/**
174
 * Create the header of a ADPCM wave file.
175
 * @param {string} bitDepthCode The audio bit depth
176
 * @param {number} numChannels The number of channels
177
 * @param {number} sampleRate The sample rate.
178
 * @param {number} numBytes The number of bytes each sample use.
179
 * @param {number} samplesLength The length of the samples in bytes.
180
 * @param {!Object} options The extra options, like container defintion.
181
 * @private
182
 */
183
function createADPCMHeader_(bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
184
  let header = createPCMHeader_(
185
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
186
  header.chunkSize = 40 + samplesLength;
187
  header.fmt.chunkSize = 20;
188
  header.fmt.byteRate = 4055;
189
  header.fmt.blockAlign = 256;
190
  header.fmt.bitsPerSample = 4;
191
  header.fmt.cbSize = 2;
192
  header.fmt.validBitsPerSample = 505;
193
  header.fact = {
194
    chunkId: 'fact',
195
    chunkSize: 4,
196
    dwSampleLength: samplesLength * 2
197
  };
198
  return header;
199
}
200
201
/**
202
 * Create the header of WAVE_FORMAT_EXTENSIBLE file.
203
 * @param {string} bitDepthCode The audio bit depth
204
 * @param {number} numChannels The number of channels
205
 * @param {number} sampleRate The sample rate.
206
 * @param {number} numBytes The number of bytes each sample use.
207
 * @param {number} samplesLength The length of the samples in bytes.
208
 * @param {!Object} options The extra options, like container defintion.
209
 * @private
210
 */
211
function createExtensibleHeader_(
212
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
213
  let header = createPCMHeader_(
214
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
215
  header.chunkSize = 36 + 24 + samplesLength;
216
  header.fmt.chunkSize = 40;
217
  header.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
218
  header.fmt.cbSize = 22;
219
  header.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
220
  header.fmt.dwChannelMask = getDwChannelMask_(numChannels);
221
  // subformat 128-bit GUID as 4 32-bit values
222
  // only supports uncompressed integer PCM samples
223
  header.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
224
  return header;
225
}
226
227
/**
228
 * Create the header of mu-Law and A-Law wave files.
229
 * @param {string} bitDepthCode The audio bit depth
230
 * @param {number} numChannels The number of channels
231
 * @param {number} sampleRate The sample rate.
232
 * @param {number} numBytes The number of bytes each sample use.
233
 * @param {number} samplesLength The length of the samples in bytes.
234
 * @param {!Object} options The extra options, like container defintion.
235
 * @private
236
 */
237
function createALawMulawHeader_(
238
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
239
  let header = createPCMHeader_(
240
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
241
  header.chunkSize = 40 + samplesLength;
242
  header.fmt.chunkSize = 20;
243
  header.fmt.cbSize = 2;
244
  header.fmt.validBitsPerSample = 8;
245
  header.fact = {
246
    chunkId: 'fact',
247
    chunkSize: 4,
248
    dwSampleLength: samplesLength
249
  };
250
  return header;
251
}
252
253
/**
254
 * Get the value for dwChannelMask according to the number of channels.
255
 * @return {number} the dwChannelMask value.
256
 * @private
257
 */
258
function getDwChannelMask_(numChannels) {
259
  /** @type {number} */
260
  let dwChannelMask = 0;
261
  // mono = FC
262
  if (numChannels === 1) {
263
    dwChannelMask = 0x4;
264
  // stereo = FL, FR
265
  } else if (numChannels === 2) {
266
    dwChannelMask = 0x3;
267
  // quad = FL, FR, BL, BR
268
  } else if (numChannels === 4) {
269
    dwChannelMask = 0x33;
270
  // 5.1 = FL, FR, FC, LF, BL, BR
271
  } else if (numChannels === 6) {
272
    dwChannelMask = 0x3F;
273
  // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR
274
  } else if (numChannels === 8) {
275
    dwChannelMask = 0x63F;
276
  }
277
  return dwChannelMask;
278
}
279